package de.idyl.crypto.zip.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;


/**
 * Write zip entries to Zip-File, encrypted or not encrypted.
 *
 * @author <a href="mailto:olaf@merkert.de">Olaf Merkert</a>
 */
public class ExtZipOutputStream implements ZipConstants {

	public ExtZipOutputStream(File file) throws IOException {
		out = new FileOutputStream(file);
	}

	protected ExtZipOutputStream(OutputStream out) {
		this.out = out;
	}

  protected OutputStream out;

	/** number of bytes written to out */
	protected int written;

	public int getWritten() {
		return this.written;
	}

  public void writeBytes(byte[] b ) throws IOException {
    out.write(b);
    written+=b.length;
  }

  public void writeShort(int v) throws IOException {
    out.write((v >>> 0) & 0xff);
    out.write((v >>> 8) & 0xff);
    written+=2;
  }

  public void writeInt(long v) throws IOException {
    out.write((int)((v >>>  0) & 0xff));
    out.write((int)((v >>>  8) & 0xff));
    out.write((int)((v >>> 16) & 0xff));
    out.write((int)((v >>> 24) & 0xff));
    written+=4;
  }

  public void writeBytes(byte[] b, int off, int len) throws IOException {
    out.write(b, off, len);
    written+=len;
  }

  // --------------------------------------------------------------------------

	protected final static short ZIP_VERSION = 20;	// version set by java.util.zip

	protected void writeFileInfo( ExtZipEntry entry ) throws IOException {
		writeShort( ZIP_VERSION );	// version needed to extract

		// general purpose bit flag - 0x0001 indicates encryption		2 bytes
    writeShort( entry.getFlag() );

    writeShort( entry.getPrimaryCompressionMethod() ); // primary compression method - 0x63==encryption

    writeInt( entry.getDosTime() );	// 2 bytes last mod file time + 2 bytes last mod file date

    writeInt( entry.getCrc() );

		// 28 bytes is the encryption overhead (caused by 256-bit AES key)
		// 2 bytes pwVerification + 16 bytes SALT + 10 bytes AUTHENTICATION

    writeInt( (int)entry.getCompressedSize() );   // compressed size
    writeInt( (int)entry.getSize() );             // uncompressed size

		writeShort( entry.getName().length() );			// file name length
		if( entry.getExtra()!=null ) {
			writeShort( entry.getExtra().length );	  // extra field length
		} else {
			writeShort( 0 );
		}
	}

	private List<ExtZipEntry> entries = new ArrayList<ExtZipEntry>();

	protected void writeDirEntry( ExtZipEntry entry ) throws IOException {
    writeInt( CENSIG );	//writeBytes( new byte[] { 0x50, 0x4b, 0x01, 0x02 } );  // directory signature
    writeShort( ZIP_VERSION );	// version made by
		writeFileInfo(entry);

    writeShort( 0x00 );   					// file comment length 								2 bytes
    writeShort( 0x00 );   					// disk number start (unused)         2 bytes
    writeShort( 0x00 );   					// internal file attributes (unsued)  2 bytes
    writeInt( 0x00 );  							// external file attributes (unused)  4 bytes

    writeInt( entry.getOffset() );	// relative offset of local header    4 bytes

		writeBytes( entry.getName().getBytes("iso-8859-1") );

		writeExtraBytes( entry );
	}

	protected void writeExtraBytes( ZipEntry entry ) throws IOException {
		byte[] extraBytes = entry.getExtra();
		if( extraBytes!=null ) {
			writeBytes( extraBytes );
		}
	}

  // --------------------------------------------------------------------------

	public void putNextEntry(ExtZipEntry entry) throws IOException {
		entries.add( entry );

		entry.setOffset( written );

		// file header signature
    writeInt( LOCSIG );

		writeFileInfo( entry );
    writeBytes( entry.getName().getBytes("iso-8859-1") );
		writeExtraBytes( entry );
	}

	/**
	 * Finishes writing the contents of the ZIP output stream without closing the
   * underlying stream. Also closes the stream.
	 */
	public void finish() throws IOException {
		int dirOffset = written;	// central directory (at end of zip file) starts here

		int startOfCentralDirectory = written;

		Iterator<ExtZipEntry> it = entries.iterator();
		while( it.hasNext() ) {
			ExtZipEntry entry = it.next();
			writeDirEntry( entry );
		}
    int centralDirectorySize = written - startOfCentralDirectory;

    writeInt( ENDSIG );		// end of central dir signature    																		4 bytes

		writeShort( 0x00 );   // number of this disk                                          			2 bytes
		writeShort( 0x00 );   // number of the disk with the start of the central directory   			2 bytes

    writeShort( entries.size() ); // total number of entries in central directory on this disk  2 bytes
    writeShort( entries.size() ); // total number of entries in the central directory           2 bytes

    writeInt( centralDirectorySize );	// size of the central directory   4 bytes

		writeInt( dirOffset );	// offset of start of central dir, with respect to starting disk		4 bytes
		writeShort( 0x00 );   	// .ZIP file comment length        																	2 bytes

    out.close();
	}

}
